/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.icpoints;

import static br.ufpe.cin.imlm2.jbeat.soot.icpoints.StmtLabel.Rethrow;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import soot.Body;
import soot.Local;
import soot.SootMethod;
import soot.Trap;
import soot.Unit;
import soot.jimple.AssignStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.Stmt;
import soot.jimple.ThrowStmt;
import soot.tagkit.Host;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.CombinedAnalysis;
import soot.toolkits.scalar.CombinedDUAnalysis;
import soot.toolkits.scalar.UnitValueBoxPair;
import br.ufpe.cin.imlm2.jbeat.soot.common.CommonOptions;
import br.ufpe.cin.imlm2.jbeat.soot.common.MethodTagger;
import br.ufpe.cin.imlm2.jbeat.soot.common.SceneWrapper;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.InterconnectingPointsTag;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.tags.StmtLabelsTag;

public class TrapInspector extends MethodTagger {

	private boolean rethrows( Trap t, Body b ) {
		Stmt handlerUnit = (Stmt) t.getHandlerUnit();
		List<Stmt> stmts = this.labelStmts( handlerUnit, b );
		if ( stmts.size() > 0 ) {
			boolean rethrows = false;
			for ( Stmt stmt : stmts ) {
				if ( stmt.hasTag( StmtLabelsTag.TAG_NAME ) ) {
					StmtLabelsTag slt = (StmtLabelsTag) stmt.getTag( StmtLabelsTag.TAG_NAME );
					if ( slt.containsLabel( Rethrow ) )
						rethrows = true;
					slt.clearLabels();
				}
			}
			return rethrows;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private List<Stmt> labelStmts( Stmt handlerUnit, Body b ) {
		UnitGraph ug = new ExceptionalUnitGraph( b );
		CombinedAnalysis combinedAnalysis = CombinedDUAnalysis.v( ug );
		Queue<UnitValueBoxPair> workList = new LinkedList<UnitValueBoxPair>(
				combinedAnalysis.getUsesOf( handlerUnit ) );
		List<UnitValueBoxPair> processedItems = new ArrayList<UnitValueBoxPair>();
		while ( !workList.isEmpty() ) {
			UnitValueBoxPair workItem = workList.remove();
			processedItems.add( workItem );
			Stmt stmt = (Stmt) workItem.getUnit();
			List<UnitValueBoxPair> uses = combinedAnalysis.getUsesOf( stmt );
			for ( UnitValueBoxPair use : uses ) {

				if ( use.getValueBox().getValue() instanceof Local ) {
					if ( !processedItems.contains( use ) ) {
						workList.add( use );
					}
				}

			}
			StmtLabelsTag slt = (StmtLabelsTag) stmt.getTag( StmtLabelsTag.TAG_NAME );
			if ( stmt.containsInvokeExpr() ) {
				slt.addLabel( StmtLabel.Call_Other_Method );
			}
			if ( stmt instanceof ReturnStmt ) {
				slt.addLabel( StmtLabel.Exception_Object_Returned );
			} else if ( stmt instanceof AssignStmt
					&& (stmt.containsFieldRef() || stmt.containsArrayRef()) ) {
				slt.addLabel( StmtLabel.Array_Field_Store );
			} else if ( stmt instanceof ThrowStmt ) {
				slt.addLabel( StmtLabel.Rethrow );
			}
		}

		List<Stmt> toBeLabeled = new ArrayList<Stmt>();
		for ( UnitValueBoxPair unitValueBoxPair : processedItems ) {
			toBeLabeled.add( (Stmt) unitValueBoxPair.getUnit() );
		}

		return toBeLabeled;
	}

	protected void checkTags( SootMethod meth ) {
		if ( meth.isConcrete() ) {
			if ( !meth.hasTag( InterconnectingPointsTag.TAG_NAME ) ) {
				meth.addTag( new InterconnectingPointsTag() );
			}
		}
	}

	@Override
	protected void tag() {
		super.tag();
		for ( Host h : this.getTargetSet() ) {
			SootMethod sootMethod = (SootMethod) h;
			Body b = sootMethod.retrieveActiveBody();
			this.addStmtLabelsTags( b );
			for ( Trap t : b.getTraps() ) {
				if ( this.rethrows( t, b ) ) {
					InterconnectingPointsTag ipt = (InterconnectingPointsTag) sootMethod
							.getTag( InterconnectingPointsTag.TAG_NAME );
					ipt.addInterconnectingPoint( t );
				}
			}
			this.removeStmtLabelsTags( b );
		}
	}

	private void addStmtLabelsTags( Body b ) {
		for ( Unit t : b.getUnits() ) {
			t.addTag( new StmtLabelsTag() );
		}
	}

	private void removeStmtLabelsTags( Body b ) {
		for ( Unit u : b.getUnits() ) {
			u.removeTag( StmtLabelsTag.TAG_NAME );
		}
	}

	@Override
	protected void resolveTargetSet() {
		//XXX CHeck this out later!
		CommonOptions ops = (CommonOptions) this.options;
		boolean exceptionInterfacesOnly = ops.isExceptionInterfacesOnly();
		this.getTargetSet().clear();
		if(exceptionInterfacesOnly){			
			for (SootMethod meth : SceneWrapper.getApplicationMethods()) {
				if(meth.isConcrete())
					this.getTargetSet().add(meth);
			}
		} else{
			this.getTargetSet().addAll(SceneWrapper.getConcreteMethods());
		}

	}



}
